﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>Identity Server Integration</title>
<link type="text/css" rel="stylesheet" href="../bootstrap.min.css" />
</head>

<body>

<div class="document-contents">

<h3>Introduction</h3>
<p><a href="http://identityserver.io/" target="_blank">Identity Server</a> is an 
open source <strong>OpenID Connect</strong> and <strong>OAuth 2.0</strong> framework. It can be used to make your 
application an <strong>authentication / single sign on server</strong>. It can also issue 
<strong>access tokens</strong> for 3rd party clients.
This document describes how you can integrate IdentityServer to your project.</p>

	<div class="bs-callout bs-callout-warning">
	<h4>Startup Project</h4>
	<p>This document assumes that you have created an ASP.NET Core based project 
	(including module zero) from <a href="/Templates">startup templates</a> and 
	made it working. I created an <a href="Startup-Template-Core.html">ASP.NET 
	Core MVC startup project</a> for demonstration.</p>
	</div>

	<h3>Installation</h3>
	<p>There are two nuget packages:</p>
	<ul>
		<li>
	<a href="https://www.nuget.org/packages/Abp.ZeroCore.IdentityServer4" target="_blank">
	<strong>Abp.ZeroCore.IdentityServer4</strong></a> is the main integration 
		package.</li>
		<li>
	<a href="https://www.nuget.org/packages/Abp.ZeroCore.IdentityServer4.EntityFrameworkCore" target="_blank">
	<strong>Abp.ZeroCore.IdentityServer4.EntityFrameworkCore</strong></a> is the 
		storage provider for EF Core.</li>
	</ul>
	<p>Since EF Core package already depends on the first one, you can only install
	<a href="https://www.nuget.org/packages/Abp.ZeroCore.IdentityServer4.EntityFrameworkCore" target="_blank">
	<strong>Abp.ZeroCore.IdentityServer4.EntityFrameworkCore</strong></a> 
	package to your project. Install it to the project contains your DbContext 
	(.EntityFrameworkCore project for default templates):</p>
	<pre>Install-Package Abp.ZeroCore.IdentityServer4.EntityFrameworkCore</pre>
	<p>Then you can add dependency to your 
	<a href="../Module-System.html">module</a> (generally, to your 
	EntityFrameworkCore project):</p>
<pre lang="cs"><strong>[DependsOn(typeof(AbpZeroCoreIdentityServerEntityFrameworkCoreModule))]</strong>
public class MyModule : AbpModule
{
    //...
}
</pre>
	<h3>Configuration</h3>
	<p>Configuring and using IdentityServer4 with Abp.ZeroCore is similar to 
	independently use IdentityServer4. You should read it&#39;s
	<a href="https://identityserver4.readthedocs.io" target="_blank">own 
	documentation</a> to understand and use it. In this document, we only show 
	additional configuration needed to integrate to Abp.ZeroCore.</p>
	<h4>Startup Class</h4>
	<p>In the ASP.NET Core <strong>Startup class</strong>, we should add IdentityServer to 
	<strong>service collection</strong> and to ASP.NET Core <strong>middleware pipeline</strong>. 
	Highlighted the <strong>differences</strong> from standard IdentityServer4 usage:</p>
	<pre lang="cs">public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        //...
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
            .AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
            .AddInMemoryClients(IdentityServerConfig.GetClients())
            <strong>.AddAbpPersistedGrants&lt;YourDbContext&gt;()</strong>
            <strong>.AddAbpIdentityServer&lt;User&gt;()</strong>;
        //...
    }

    public void Configure(IApplicationBuilder app)
    {
        //...
        app.UseIdentityServer();
        //...
    }
}</pre>
	<p>
            I added <strong>services.AddIdentityServer()</strong> just after
			<strong>IdentityRegistrar.Register(services)</strong> and added 
			app.UseIdentityServer() just afterAuthConfigurer.Configure in the 
			startup project.</p>
	<p>
            <strong>AddAbpIdentityServer&lt;User&gt;()</strong> is the main method 
	that integrates IdentityServer4 with Abp.ZeroCore and Microsoft ASP.NET Core 
	Identity. It gets your User class a generic parameter.&nbsp;Add this just 
			after adding Microsoft ASP.NET Core Identity.</p>
	<h4>IdentityServerConfig Class</h4>
	<p>We have used IdentityServerConfig class to get identity resources, api 
	resources and clients. You can find more information about this class in 
	it&#39;s own
	<a href="https://identityserver4.readthedocs.io/en/release/quickstarts/1_client_credentials.html" target="_blank">
	documentation</a>. For the simplest case, it can be a static class like 
	below:</p>
	<pre lang="cs">public static class IdentityServerConfig
{
    public static IEnumerable&lt;ApiResource&gt; GetApiResources()
    {
        return new List&lt;ApiResource&gt;
        {
            new ApiResource("default-api", "Default (all) API")
        };
    }

    public static IEnumerable&lt;IdentityResource&gt; GetIdentityResources()
    {
        return new List&lt;IdentityResource&gt;
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResources.Phone()
        };
    }

    public static IEnumerable&lt;Client&gt; GetClients()
    {
        return new List&lt;Client&gt;
        {
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ClientCredentials.Union(GrantTypes.ResourceOwnerPassword),
                AllowedScopes = {"default-api"},
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                }
            }
        };
    }
}</pre>
	<h4>DbContext Changes</h4>
	<p><strong>AddAbpPersistentGrants()</strong> method is used to save consent responses to the 
	persistent data store. In order to use it, <strong>YourDbContext</strong> must implement
	<strong>IAbpPersistedGrantDbContext</strong> interface as shown below:</p>
	<pre lang="cs">public class YourDbContext : AbpZeroDbContext&lt;Tenant, Role, User, YourDbContext&gt;, <strong>IAbpPersistedGrantDbContext</strong>
{
    <strong>public DbSet&lt;PersistedGrantEntity&gt; PersistedGrants { get; set; }</strong>

    public YourDbContext(DbContextOptions&lt;YourDbContext&gt; options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        <strong>modelBuilder.ConfigurePersistedGrantEntity();</strong>
    }
}</pre>
	<p>IAbpPersistedGrantDbContext defines <strong>PersistedGrants</strong> 
	DbSet. We also should call modelBuilder.<strong>ConfigurePersistedGrantEntity()</strong> 
	extension method as shown above in order to configure EntityFramework for
	<strong>PersistedGrantEntity</strong>.</p>
	<p>Notice that this change in YourDbContext cause a new database migration. 
	So, remember to use &quot;Add-Migration&quot; and &quot;Update-Database&quot; commands to update 
	your database.</p>
	<p>IdentityServer4 will continue to work even if you don&#39;t call 
	AddAbpPersistedGrants&lt;YourDbContext&gt;() extension method, but user consent 
	responses will be stored an in-memory data store in that case (which is 
	cleared when you restart your application).</p>
	<h4>JWT Authentication Middleware</h4>
	<p>If we want to authorize clients against the same application we can use
	<a href="http://docs.identityserver.io/en/release/topics/apis.html?highlight=UseIdentityServerAuthentication#the-identityserver-authentication-middleware" target="_blank">
	IdentityServer authentication middleware</a> for that.</p>
	<p>First, install IdentityServer4.AccessTokenValidation package from nuget 
	to your project:</p>
	<pre>Install-Package IdentityServer4.AccessTokenValidation</pre>
	<p>Then we can add the middleware to Startup class as shown below:</p>
	<pre lang="cs">app.UseIdentityServerAuthentication(
    new IdentityServerAuthenticationOptions
    {
        Authority = "http://localhost:62114/",
        RequireHttpsMetadata = false,
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });</pre>
	<p>I added this just after app.UseIdentityServer() in the startup project.</p>
	<h3>Test</h3>
	<p>Now, our identity server is ready to get requests from clients. We can 
	create a console application to make requests and get responses.</p>
	<ul>
		<li>Create a new <strong>Console Application</strong> inside your solution.</li>
		<li>Add <strong>IdentityModel</strong> nuget package to the console 
		application. This package is used to create clients for OAuth endpoints.</li>
	</ul>
	<p>While <strong>IdentityModel</strong> nuget package is enough to create a client and consume your API, I want to 
	show to use API in more type safe way: We will convert incoming data to DTOs 
	returned by application services.</p>
	<ul>
		<li>Add reference to <strong>Application</strong> layer from the console 
		application. This will allow us to use same DTO classes returned by the 
		application layer in the client side.</li>
		<li>Add <strong>Abp.Web.Common</strong> nuget package. This will allow 
		us to use AjaxResponse class defined in ASP.NET Boilerplate class. 
		Otherwise, we will deal with raw JSON strings to handle the server 
		response.</li>
	</ul>
	<p>Then we can change Program.cs as shown below:</p>
	<pre lang="cs">using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Json;
using IdentityModel.Client;
using Abp.MultiTenancy;
using Abp.Web.Models;
using IdentityServerIntegrationDemo.Users.Dto;
using Newtonsoft.Json;

namespace IdentityServerIntegrationDemo.ConsoleApiClient
{
    class Program
    {
        static void Main(string[] args)
        {
            RunDemoAsync().Wait();
            Console.ReadLine();
        }

        public static async Task RunDemoAsync()
        {
            var accessToken = await GetAccessTokenViaOwnerPasswordAsync();
            await GetUsersListAsync(accessToken);
        }

        private static async Task&lt;string&gt; GetAccessTokenViaOwnerPasswordAsync()
        {
            var disco = await DiscoveryClient.GetAsync("http://localhost:62114");

<strong>            var httpHandler = new HttpClientHandler();
            httpHandler.CookieContainer.Add(new Uri("http://localhost:62114/"), new Cookie(MultiTenancyConsts.TenantIdResolveKey, "1")); //Set TenantId
            var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret", httpHandler);
            var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("admin", "123qwe");
</strong>
            if (tokenResponse.IsError)
            {
                Console.WriteLine("Error: ");
                Console.WriteLine(tokenResponse.Error);
            }

            Console.WriteLine(tokenResponse.Json);

            return tokenResponse.AccessToken;
        }

        private static async Task GetUsersListAsync(string accessToken)
        {
            var client = new HttpClient();
            client.SetBearerToken(accessToken);

<strong>            var response = await client.GetAsync("http://localhost:62114/api/services/app/user/getUsers");
</strong>            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
                return;
            }

            var content = await response.Content.ReadAsStringAsync();
<strong>            var ajaxResponse = JsonConvert.DeserializeObject&lt;AjaxResponse&lt;PagedResultDto&lt;UserListDto&gt;&gt;&gt;(content);
</strong>            if (!ajaxResponse.Success)
            {
                throw new Exception(ajaxResponse.Error?.Message ?? "Remote service throws exception!");
            }

            Console.WriteLine();
            Console.WriteLine("Total user count: " + ajaxResponse.Result.TotalCount);
            Console.WriteLine();
            foreach (var user in ajaxResponse.Result.Items)
            {
                Console.WriteLine($"### UserId: {user.Id}, UserName: {user.UserName}");
                Console.WriteLine(user.ToJsonString(indented: true));
            }
        }
    }
}</pre>
	<p>Before running this application, ensure that your web project is up and 
	running, because this console application will make request to the web 
	application. Also, ensure that the requesting port (62114) is the same of 
	your web application.</p>
	<h3>Source Code</h3>
	<p>You can see source code of this tutorial here:
	<a href="https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/IdentityServerDemo">
	https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/IdentityServerDemo</a>.
	<a href="https://github.com/aspnetboilerplate/aspnetboilerplate-samples/commit/dcc1033a65bc1cd95ec6284a89a22859ee3545ef" target="_blank">
	This commit</a> shows all changes done to the startup project.</p>

</div>

</body>

</html>
